package export;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.ParseException;
import java.util.List;

import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;

import neuron.Coord;
import neuron.Dendrite;
import neuron.Network;
import neuron.Neuron;
import neuron.Segment;
import neuron.file.AuxData;
import neuron.file.NeuroLucidaLoader;

import org.apache.xmlgraphics.java2d.GraphicContext;
import org.apache.xmlgraphics.java2d.ps.EPSDocumentGraphics2D;

import neuron.geometry.Transform;

public class ImageToEPSExport {

	Neuron n;
	Network net;
	EPSDocumentGraphics2D eps;
	
	float NEURON_STROKE = 2;

	
	public static FileFilter epsFilter = new FileFilter() {
		public String getDescription() { return "Postscript files (*.EPS)"; }
		public boolean accept(File f) { return f.isDirectory() || f.getName().toUpperCase().endsWith(".EPS"); } 
	};

	static FilenameFilter neuroFileFilter = new FilenameFilter() {
		@Override
		public boolean accept(File dir, String name) {
			return name.toUpperCase().endsWith(".ASC") ||
				   name.toUpperCase().endsWith(".neurons");
		}
	};

	
	public static FileFilter dirFilter = new FileFilter() {
		public String getDescription() { return "Directories"; }
		public boolean accept(File f) { return f.isDirectory(); } 
	};

	
	public ImageToEPSExport(Neuron neuron) {
		this.n = neuron;

		Transform.translateToOrigo(n);
		Transform.rotateApicalToYAxis(n);
		Transform.rotateMaxVariation(n);
	}

	public ImageToEPSExport(Network network) {
		this.net = network;
	}

	double[] planes;
	
	public void setPlanes(double[] p) { this.planes = p; }

	
	int minx=0, maxx=0, miny=0, maxy=0;
	double wz;
	
	private int[] project(Coord c)
	{
		double s = 2;
		double dz = 4;//400;
		int[] p = new int[]{ (int) Math.round(c.x*s / (dz + c.z*wz)), (int) Math.round(c.y*s / (dz + c.z*wz)) };
//		System.out.println(String.format("%.2f, %.2f, %.2f -> %d, %d", c.x, c.y, c.z, p[0], p[1]));
		if (p[0] < minx) minx = p[0];
		if (p[0] > maxx) maxx = p[0];
		if (p[1] < miny) miny = p[1];
		if (p[1] > maxy) maxy = p[1];
		return p;
	}
	
	double PATH_RESOLUTION = 1.0; 
	
	private void outputPath(Coord prev, List<Coord> lst)
	{
		if (prev == null) prev = lst.get(0);
		for (Coord c : lst) {
			double d = prev.distanceTo(c);
			if (d < PATH_RESOLUTION) continue; // skip short paths
			int[] a = project(prev);
			int[] b = project(c);
			if (a[1] > h || b[1] > h) continue;
			eps.drawLine(a[0], a[1], b[0], b[1]);
			prev = c;
		}
	}
	
	private void outputSegment(Coord last, Segment s)
	{
		outputPath(last, s.getPoints());
		for (Segment b : s.getBranches()) {
			outputSegment(s.lastCoordinate(), b);
		}
	}
	
	private void outputDendrite(Dendrite d)
	{
		if (d == null) return;
		outputSegment(null, d.root);
	}
	
	private void outputNeuron(Neuron nn)
	{
		wz = 1.0/(nn.getAllCoordinates().maxZ() - nn.getAllCoordinates().minZ());
		
		// output soma
		Coord c = nn.getSoma().center();
		double area = nn.getSoma().planarArea();
		if (area < 5) area = 5.0;
		int[] a = project(c);
		int w = (int) (4*Math.sqrt(area));
		eps.fillArc(a[0]-w/2, a[1]-w/2, w, w, 0, 360);

		// output segments
		for (Dendrite d : nn.basalDendrites()) {
			int[] p = project(d.root.firstCoordinate());
			outputDendrite(d);
		}
		if (nn.getApicalDendrite() != null)
			outputDendrite(nn.getApicalDendrite());
		if (nn.getAxon() != null && net != null) { // FIXME only draw axons in networks
			Color clr = eps.getColor();
			eps.setColor(new Color(82, 186, 0));//	eps.setColor(Color.gray);
			outputDendrite(nn.getAxon());
			eps.setColor(clr);
		}
	}
	
	double ycrop = 1.0;
	public void setYCrop(double rel) { ycrop = rel; }

	int w=Integer.MAX_VALUE,h=Integer.MAX_VALUE;
	
	public void export(File out) throws IOException
	{
		FileOutputStream fos = new FileOutputStream(out);
		BufferedOutputStream bos = new BufferedOutputStream(fos);

		// find bounding box
		eps = new EPSDocumentGraphics2D(false);
		eps.setGraphicContext(new GraphicContext());
		eps.setupDocument(new ByteArrayOutputStream(), 1000, 1000);

		if (n != null) outputNeuron(n);
		else {
			ycrop = 0.5;
			for (Neuron nn : net) outputNeuron(nn);
		}
		System.out.println("MIN/MAX " + minx + "-" + maxx + ", " + miny  + "-" + maxy);

		w = maxx - minx;
		h = (int) ((maxy - miny) * ycrop);		
		int ty = maxy;

		eps = new EPSDocumentGraphics2D(false);
		eps.setGraphicContext(new GraphicContext());
		/*
		CoordSet coords = n.getAllCoordinates();
		Coord min = new Coord(coords.minX(), coords.minY(), 0, 1.0);
		Coord max = new Coord(coords.maxX(), coords.maxY(), 0, 1.0);
		int[] a = project(min);
		int[] b = project(max);
		
		System.out.println("BBOX " + a[0] + ", " + a[1] + " / " + b[0] + ", " + b[1]);
		System.out.println("BBOX W " + Math.abs(a[0] - b[0]) + ", H " + Math.abs(a[1] - b[1]));
		int w = Math.abs(a[0] - b[0]);
		int h = Math.abs(a[1] - b[1]);
//		int w = 250, h = 600;
		*/
		eps.setupDocument(bos, w+50, h+50);
//		eps.drawRect(0,0,w,h);

		// draw title
/*		if (n != null) {
			String file = (String) n.getMetadata("FILE");
			if (file != null) {
				AffineTransform at = eps.getTransform();
				eps.setFont(new Font("sans", Font.BOLD, 10));
				Rectangle2D r = eps.getFontMetrics().getStringBounds(file, eps);
				eps.translate((w+50-r.getWidth())/2, 20);
				eps.drawString(file, 0, 0);
				eps.setTransform(at);
			}
		}
	*/	
		eps.setStroke(new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
		
		// soma is at origo, so translate accordingly
		eps.translate(w/2+25, ty+25);
		eps.scale(0.9, -0.9);

		// draw planes
		if (planes != null) {
			//piaSoma, piaSoma-piaLL4, 0, piaSoma-piaWm
			double piaSoma = planes[0];
			double piaLL4 = planes[1];
			Stroke st = eps.getStroke();
			AffineTransform at = eps.getTransform();
			
			// set stroke / color / font
			eps.setColor(Color.darkGray);
			eps.setStroke(new BasicStroke(1.5f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 0, new float[]{ 7 }, 0));
			eps.setFont(new Font("sans", Font.BOLD, 10));

			int pp[] = project(new Coord(0, piaSoma, 0, 1.0));
			eps.drawLine(-(w+50)/2, pp[1], (w+50)/2, pp[1]);
			int p4[] = project(new Coord(0, piaLL4, 0, 1.0));
			eps.drawLine(-(w+50)/2, p4[1], (w+50)/2, p4[1]);
			
			eps.scale(1.0, -1.0);
			eps.translate(w-80, -(pp[1] + 5));
			eps.drawString("PIA", 0, 0);
			eps.translate(0, -(p4[1]-pp[1]));
			eps.drawString("IV", 0, 0);
			eps.translate(0, 17);
			eps.drawString("V", 0, 0);
			
			// restore
			eps.setTransform(at);
			eps.setStroke(st);
			eps.setColor(Color.black);
		}
		
		// draw neuron
		eps.setStroke(new BasicStroke(NEURON_STROKE, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
		eps.setColor(new Color(13, 64, 0));
		if (n != null) outputNeuron(n);
		else {
			for (Neuron nn : net) outputNeuron(nn);
		}
		
		// scale bar
		eps.setColor(Color.black);
		eps.translate(w-110, -80);
		eps.setStroke(new BasicStroke(2, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL));
		eps.drawLine(0, 0, 100, 0);
		eps.scale(1.0, -1.0);
		eps.setFont(new Font("sans", Font.BOLD, 20));
		eps.drawString("100 um", 0, 5);
		
		eps.finish();
		
		bos.close();
		fos.close();		
	}


	public static void export(File neuron, Component parent) throws FileNotFoundException, IOException, ParseException {
		// init save dialog
		JFileChooser jfc = new JFileChooser(neuron);
		
		if (neuron.isFile()) jfc.setFileFilter(epsFilter);
		else jfc.setFileFilter(dirFilter);
		
		String fileName = neuron.getName();
		if (neuron.isFile() && fileName.indexOf('.') != -1) {
			fileName = fileName.substring(0, fileName.indexOf('.')) + ".eps";
		}
		
		// show dialog
		jfc.setSelectedFile(new File(System.getProperty("user.home"), (neuron.isFile() ? fileName : "")));
		if (neuron.isDirectory()) jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		int r = jfc.showSaveDialog(parent);
		
		// if save pressed, save to file
		if (r == JFileChooser.APPROVE_OPTION) {
			
			if (neuron.isFile()) {
				// write single eps
				File epsFile = jfc.getSelectedFile();
				Neuron n = NeuroLucidaLoader.load(neuron).get(0);
				ImageToEPSExport ex = new ImageToEPSExport(n);
				ex.setPlanes(AuxData.getPlanes(neuron));
				ex.export(epsFile);
			} else {
				// write directory
				File[] files = neuron.listFiles(neuroFileFilter);
				for (File f : files) {
					try {
						String eps = f.getName();
						eps = eps.substring(0, eps.indexOf('.')) + ".eps";
						Neuron n = NeuroLucidaLoader.load(f).get(0);
						ImageToEPSExport ex = new ImageToEPSExport(n);
						ex.setPlanes(AuxData.getPlanes(f));
						ex.export(new File(neuron, eps));
						System.out.println("WRITE " + f + " TO " + new File(jfc.getSelectedFile(), eps));
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

	
	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
		ImageToEPSExport ex = new ImageToEPSExport(NeuroLucidaLoader.load(new File("070311_exp4_zA.ASC")).get(0));
		ex.ycrop = 1.0;
	//	EPSExport ex = new EPSExport(NeuroLucidaLoader.load(new File("070517_exp5_zC.ASC")).get(0));
		ex.setPlanes(new double[] { 100, 200, 0, -500 });
		ex.export(new File("/home/risto/neurotest.eps"));
	}

}
